import { ILocalStore } from 'hooks/useLocalStore';
import { Base64 } from 'js-base64';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import {
    normalizeReadmeContent,
    normalizeRepoContributors,
    normalizeRepository,
    ReadmeContentApi,
    ReadmeContentModel,
    RepoContributorsApi,
    RepoContributorsModel,
    RepositoryApi,
    RepositoryModel,
} from 'store/models';
import RootStore from 'store/RootStore';
import ApiStore, { FetchMeta } from 'store/RootStore/ApiStore';

import { RepoInfoRequest } from './RepositoryStore.types';

type PrivateFields =
    | '_contributors'
    | '_languages'
    | '_readme'
    | '_repository'
    | '_readmeMeta'
    | '_contributorsMeta'
    | '_languagesMeta'
    | '_repositoryMeta';

export default class RepositoryStore implements ILocalStore {
    private readonly _ApiStore = new ApiStore();
    private _contributors: RepoContributorsModel[] = [];
    private _repository?: RepositoryModel;
    private _languages: Record<string, number> = {};
    private _readme?: ReadmeContentModel;
    private _languagesMeta: FetchMeta = FetchMeta.INITIAL;
    private _readmeMeta: FetchMeta = FetchMeta.INITIAL;
    private _contributorsMeta: FetchMeta = FetchMeta.INITIAL;
    private _repositoryMeta: FetchMeta = FetchMeta.INITIAL;
    private readonly _fetchMetaReaction: IReactionDisposer;

    constructor(user?: string, repository?: string) {
        makeObservable<RepositoryStore, PrivateFields>(this, {
            _contributors: observable.ref,
            _languages: observable.ref,
            _readme: observable,
            _repository: observable.ref,
            _readmeMeta: observable,
            _contributorsMeta: observable,
            _languagesMeta: observable,
            _repositoryMeta: observable,
            contributors: computed,
            languages: computed,
            readme: computed,
            repository: computed,
            _fetchRequestsMeta: computed,
            fetchContributors: action.bound,
            fetchRepoLanguages: action.bound,
            fetchReadme: action.bound,
            fetchRepository: action.bound,
        });

        if (user && repository) {
            this.fetchRepository(user, repository);
            this.fetchContributors({ repo: repository, owner: user });
            this.fetchRepoLanguages({ repo: repository, owner: user });
            this.fetchReadme({ repo: repository, owner: user });
        }

        this._fetchMetaReaction = reaction(
            () => this._fetchRequestsMeta,
            (meta) => {
                RootStore.pageLoader.getFetchRequestsMeta(meta);
            },
        );
    }

    get _fetchRequestsMeta(): FetchMeta[] {
        return [this._readmeMeta, this._languagesMeta, this._contributorsMeta, this._repositoryMeta];
    }
    get contributors(): RepoContributorsModel[] {
        return this._contributors;
    }
    get languages(): Record<string, number> {
        return this._languages;
    }
    get readme(): ReadmeContentModel | undefined {
        return this._readme;
    }
    get repository(): RepositoryModel | undefined {
        return this._repository;
    }

    fetchRepository = async (owner: string, repository: string): Promise<void> => {
        this._contributorsMeta = FetchMeta.FETCHING;
        try {
            const result = await this._ApiStore.getApiRequest<RepositoryApi>(`/repos/${owner}/${repository}`);
            runInAction(() => {
                this._repository = normalizeRepository(result.data);
                this._repositoryMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._repository = undefined;
                this._repositoryMeta = FetchMeta.ERROR;
            });
        }
    };

    fetchContributors = async (data: RepoInfoRequest): Promise<void> => {
        this._contributorsMeta = FetchMeta.FETCHING;
        const { owner, repo } = data;
        try {
            const result = await this._ApiStore.getApiRequest<RepoContributorsApi[]>(
                `/repos/${owner}/${repo}/contributors`,
                { params: { per_page: 4 } },
            );
            runInAction(() => {
                this._contributors = normalizeRepoContributors(result.data);
                this._contributorsMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._contributorsMeta = FetchMeta.ERROR;
                this._contributors = [];
            });
        }
    };

    fetchRepoLanguages = async (data: RepoInfoRequest): Promise<void> => {
        this._languagesMeta = FetchMeta.FETCHING;
        const { owner, repo } = data;
        try {
            const result = await this._ApiStore.getApiRequest<Record<string, number>>(
                `/repos/${owner}/${repo}/languages`,
            );
            runInAction(() => {
                this._languages = result.data;
                this._languagesMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._languagesMeta = FetchMeta.ERROR;
                this._languages = {};
            });
        }
    };

    fetchReadme = async (data: RepoInfoRequest): Promise<void> => {
        this._readmeMeta = FetchMeta.FETCHING;
        const { owner, repo } = data;
        try {
            const result = await this._ApiStore.getApiRequest<ReadmeContentApi>(`/repos/${owner}/${repo}/readme`);
            runInAction(() => {
                this._readme = normalizeReadmeContent({ ...result.data, content: Base64.decode(result.data.content) });
                this._readmeMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._readmeMeta = FetchMeta.ERROR;
                this._readme = undefined;
            });
        }
    };

    destroy(): void {
        this._fetchMetaReaction();
    }
}
